package util

import (
	"bytes"
	"context"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/axgle/mahonia"
	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
	"github.com/go-basic/uuid"
	jsoniter "github.com/json-iterator/go"
	"github.com/kataras/iris/v12"
	"github.com/pkg/errors"
	"github.com/qf-tech/qftech-base/pkg/log"
	"golang.org/x/net/html"
)

// TransJsonReq2Struct def
func TransJsonReq2Struct(ctx iris.Context, v interface{}) error {
	log := log.GetLogger().GetCtx(ctx.Request().Context()).Sugar()

	// 判断消息体是否是application/json
	isJSON := false
	for _, v := range ctx.Request().Header["Content-Type"] {
		isJSON = strings.Contains(v, "application/json")
	}

	if !isJSON {
		return errors.New("request content type is not json")
	}

	tmpData, err := ioutil.ReadAll(ctx.Request().Body)
	if err != nil {
		return err
	}
	reqData := string(tmpData)

	err = jsoniter.UnmarshalFromString(reqData, v)
	if err != nil {
		return err
	}

	log.Warnf("req uri: %s method: %s, req data: %s", ctx.Request().RequestURI, ctx.Request().Method, reqData)
	return nil
}

// calculate hash
func CalculateHash(toBeHashed string) string {
	hashInBytes := sha256.Sum256([]byte(toBeHashed))
	hashStr := hex.EncodeToString(hashInBytes[:])
	return hashStr
}

func HttpGet(url string, headers map[string]string, timeout time.Duration) (string, error) {
	c := &http.Client{Timeout: timeout}
	if strings.HasPrefix(url, "https://") {
		c.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	for k, v := range headers {
		req.Header.Add(k, v)
	}

	resp, err := c.Do(req)
	if err != nil {
		return "", err
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(body), nil
}

func ConvertToString(src string, srcCode string, tagCode string) string {
	srcCoder := mahonia.NewDecoder(srcCode)
	srcResult := srcCoder.ConvertString(src)
	tagCoder := mahonia.NewDecoder(tagCode)
	_, cdata, _ := tagCoder.Translate([]byte(srcResult), true)
	result := string(cdata)
	return result
}

type Size struct {
	Width  int64
	Height int64
}
type ScreenShotConfig struct {
	URL      string
	SaveName string
	SaveDir  string
	Quality  int64
	Window   Size
	Region   Size
}

// Screenshot
func Screenshot(ctx context.Context, config ScreenShotConfig) (string, error) {
	log := log.GetLogger().GetCtx(ctx).Sugar()
	// 创建一个上下文
	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	// 设置浏览器选项
	opts := append(chromedp.DefaultExecAllocatorOptions[:],
		chromedp.Flag("headless", true),
		chromedp.Flag("disable-gpu", true),
		chromedp.Flag("no-sandbox", true),
		chromedp.Flag("disable-dev-shm-usage", true),
		chromedp.Flag("remote-debugging-port", "19222"),
	)
	if config.Window.Width > 0 && config.Window.Height > 0 {
		opts = append(opts, chromedp.WindowSize(int(config.Window.Width), int(config.Window.Height)))
	}

	allocCtx, cancel := chromedp.NewExecAllocator(ctx, opts...)
	defer cancel()

	// 创建一个浏览器实例
	ctx, cancel = chromedp.NewContext(allocCtx)
	defer cancel()

	// 导航到指定的URL
	var buf []byte
	err := chromedp.Run(ctx, chromedp.Navigate(config.URL), chromedp.Sleep(3*time.Second), chromedp.ActionFunc(func(ctx context.Context) error {
		// 获取页面截图
		var err error
		if config.Region.Width == 0 && config.Region.Height == 0 {
			buf, err = page.CaptureScreenshot().WithQuality(config.Quality).Do(ctx)
		} else {
			clip := &page.Viewport{
				X:      0,
				Y:      0,
				Width:  float64(config.Region.Width),
				Height: float64(config.Region.Height),
				Scale:  1,
			}
			buf, err = page.CaptureScreenshot().WithQuality(config.Quality).WithClip(clip).Do(ctx)
		}
		if err != nil {
			return errors.Wrap(err, "capture screen shot error")
		}
		return nil
	}))
	if err != nil {
		return "", errors.Wrap(err, "chromedp run error")
	}

	// 确保目标路径目录存在
	if err := os.MkdirAll(config.SaveDir, os.ModePerm); err != nil {
		return "", errors.Wrap(err, "mkdir error")
	}

	// 将截图保存到文件
	filePath := fmt.Sprintf("%s/%s", config.SaveDir, config.SaveName)
	err = ioutil.WriteFile(filePath, buf, 0644)
	if err != nil {
		log.Errorf("save file err: %v", err)
		return "", errors.Wrap(err, "save file error")
	}

	return filePath, nil
}

func SetNodeAttr(n *html.Node, key string, val string) *html.Node {
	if n == nil {
		return nil
	}

	for i, a := range n.Attr {
		// change attr
		if a.Key == key {
			n.Attr[i].Val = val
			return n
		}
	}

	// add attr
	n.Attr = append(n.Attr, html.Attribute{
		Key: key,
		Val: val,
	})

	return n
}

func GetNodeAttr(n *html.Node, key string) string {
	if n == nil {
		return ""
	}

	for _, a := range n.Attr {
		if a.Key == key {
			return a.Val
		}
	}

	return ""
}

func GenShortUUID() string {
	tmpSlice := strings.Split(uuid.New(), "-")
	uuid := tmpSlice[len(tmpSlice)-1]
	return uuid
}

// MakeBodyFromTemplateV2 从本地的文件系统中加载并渲染指定的模板，
// 模板路径示例：templates/mail/auth/activate。
// 请根据模板目录下的 README 传入对应的 data 参数。
func MakeBodyFromTemplateV2(templatePath string, data interface{}) ([]byte, error) {
	if !strings.HasSuffix(templatePath, ".tmpl") {
		templatePath += ".tmpl"
	}

	t, err := template.ParseFiles(templatePath)
	if err != nil {
		return nil, errors.Wrap(err, "parse")
	}

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, data); err != nil {
		return nil, errors.Wrap(err, "execute")
	}
	return tpl.Bytes(), nil
}

func TransBytesToOther(byteSize int64) string {
	if byteSize >= 1024*1024*1024*1024 { // TB级别
		return fmt.Sprintf("%.2f TB", float64(byteSize)/(1024*1024*1024*1024))
	} else if byteSize >= 1024*1024*1024 { // GB级别
		return fmt.Sprintf("%.2f GB", float64(byteSize)/(1024*1024*1024))
	} else if byteSize >= 1024*1024 { // MB级别
		return fmt.Sprintf("%.2f MB", float64(byteSize)/(1024*1024))
	} else if byteSize >= 1024 { // KB级别
		return fmt.Sprintf("%.2f KB", float64(byteSize)/1024)
	} else {
		return fmt.Sprintf("%d Bytes", byteSize)
	}
}

func IntPtr(i int) *int {
	return &i
}

// 驼峰单词转下划线单词
func CamelCaseToUdnderscore(s string) string {
	var output []rune
	for i, r := range s {
		if i == 0 {
			output = append(output, unicode.ToLower(r))
		} else {
			if unicode.IsUpper(r) {
				output = append(output, '_')
			}

			output = append(output, unicode.ToLower(r))
		}
	}
	return string(output)
}

func DealSorts(ins []string) []string {
	// ["col1 ASC", "col2 DESC"]
	ous := make([]string, 0, len(ins))
	var direction string
	var field string
	for _, item := range ins {
		if strings.HasPrefix(item, "-") {
			direction = "DESC"
			field = strings.Trim(item, "-")
		} else {
			direction = "ASC"
			field = strings.Trim(item, "+")
		}
		ous = append(ous, fmt.Sprintf("%s %s", CamelCaseToUdnderscore(field), direction))
	}
	return ous
}
